package org.tigris.mappedlexer.implementation;

import org.tigris.mappedlexer.Lexer;
import org.tigris.mappedlexer.exception.ParseException;
import org.tigris.mappedlexer.io.CharInputSequence;
import org.tigris.mappedlexer.validator.Validator;

/**
 * A templated class offering the infrastructure for the derived classes.
 * 
 * @author Sam Huang
 */
public class SimpleLexer implements Lexer {

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public static char getCounterpart(char symbol) {
		switch (symbol) {
		case '{':
			return '}';
		case '<':
			return '>';
		case '(':
			return ')';
		case '\"':
			return '\"';
		default:
			return ']';
		}
	}

	protected Validator spaceVerifier = null;

	protected Validator alphaVerifier = null;

	protected Validator alphaNumericVerifier = null;

	protected Validator numericVerifier = null;

	/**
	 * The token.
	 */
	protected String tk;

	/**
	 * Character source.
	 */
	protected CharInputSequence inputSource = null;

	/**
	 * Default constructor.
	 * 
	 */
	protected SimpleLexer() {

	}

	/**
	 * Constructor with arugment as input source.
	 * 
	 * @param s
	 *            the character sequence
	 */
	public SimpleLexer(CharInputSequence s) {
		this.inputSource = s;
	}

	/**
	 * Step back the pointer in [length] characters.
	 * 
	 * @param length
	 *            the length to move back
	 */
	public void backward(int length) {
		inputSource.backward(length);
	}

	/**
	 * Step back the pointer in [token.length()] characters.
	 * 
	 * @param token
	 *            move back the length of the token
	 */
	public void backward(String token) {
		inputSource.backward(token.length());
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public void close() throws Exception {
		inputSource.close();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public void error(String message) throws ParseException {
		ParseException pex = new ParseException();
		pex.setFileName(getSourceName());
		pex.setColumn(getColumn());
		pex.setLine(getLine());
		pex.setLineNumber(getLineNumber());
		pex.setMessage(message);
		throw pex;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String forward(int length) throws Exception {
		return inputSource.forward(length);
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String forward(String token) throws Exception {
		return inputSource.forward(token.length());
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String forwardUntil(char chr) throws Exception {
		return inputSource.forwardUntil(chr);
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String forwardUntil(String token) throws Exception {
		StringBuffer buffer = new StringBuffer();

		// TODO be care about the hierarchy "{" and "}"
		do {
			tk = nexttok();
			if (tk.equals("\"")) {
				buffer.append(skipQutedString());
			} else if (tk.equals("+") || tk.equals("-") || tk.equals("/")
					|| tk.equals("*") || tk.equals("\\") || tk.equals("%")
					|| tk.equals("&") || tk.equals("|") || tk.equals("!")
					|| tk.equals("^") || tk.equals("=") || tk.equals("$")) {
				buffer.append(tk);
			} else {
				buffer.append(tk).append(" ");
			}
		} while (!tk.equals(token));
		return buffer.toString();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String forwardUntil(Validator v) throws Exception {
		char chr;
		chr = nextchr();
		StringBuffer buffer = new StringBuffer();
		while (!v.validate(chr)) {
			chr = nextchr();
			buffer.append(chr);
		}
		inputSource.backward(1);
		return buffer.toString();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public int getColumn() {
		return inputSource.getColumn();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String getLine() {
		return inputSource.getLine();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public int getLineNumber() {
		return inputSource.getLineNumber();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String getSourceName() {
		return inputSource.getName();
	}

	/**
	 * next non-space character.
	 */
	public char nextchar() throws Exception {
		char chr;

		do {
			chr = nextchr();
		} while (spaceVerifier.validate(chr));

		return chr;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public char nextchar(char expected) throws Exception {
		char chr = this.nextchr();
		if (chr != expected) {
			ParseException pex = new ParseException();
			pex.setFileName(getSourceName());
			pex.setColumn(getColumn());
			pex.setLine(getLine());
			pex.setLineNumber(getLineNumber());
			pex.setExpected(chr);
			pex.setToken(String.valueOf(chr));
			throw pex;
		}
		return chr;
	}

	/**
	 * get the next character
	 * 
	 * @return the next char.
	 */
	public char nextchr() throws Exception {
		return inputSource.nextchr();
	}

	/**
	 * Proceed to next line.
	 * 
	 */
	public void nextLine() throws Exception {
		inputSource.nextLine();
	}

	/**
	 * Return the next token.
	 * 
	 */
	public String nexttok() throws Exception {
		StringBuffer buffer = new StringBuffer();
		char chr = nextchar();
		chr = skipComment(chr);
		if (alphaVerifier.validate(chr) || numericVerifier.validate(chr)) {
			do {
				buffer.append(chr);
				chr = nextchr();
			} while (alphaNumericVerifier.validate(chr)
					&& !spaceVerifier.validate(chr));
			backward(1);
			tk = buffer.toString();
		} else {
			tk = String.valueOf(chr);
		}
		return tk;
	}

	/**
	 * @param chr
	 * @return
	 */
	protected char skipComment(char chr) throws Exception {
		if (chr == '/') {
			char nextChar = nextchr();
			if (nextChar == '/') {
				String singleLine = skipSingleLineComment();
				System.out.println(singleLine);
				chr = nextchar();
			} else if (nextChar == '*') {
				skipMultiLineComment();
				chr = nextchar();
			} else {
				backward(1);
			}
		}
		return chr;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String nexttok(int length) throws Exception {
		tk = this.nexttok();
		if (tk.length() != length) {
			ParseException pex = new ParseException();
			pex.setFileName(getSourceName());
			pex.setColumn(getColumn());
			pex.setLine(getLine());
			pex.setLineNumber(getLineNumber());
			pex.setExpected(new String[] {});
			pex.setExpected(length);
			pex.setToken(tk);
			throw pex;
		}
		return tk;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String nexttok(String... expected) throws Exception {
		tk = this.nexttok();
		validate(expected);
		return tk;
	}

	/**
	 * @param expected
	 * @throws ParseException
	 */
	private void validate(String... expected) throws ParseException {
		for (String exp : expected) {
			if (tk.equals(exp)) {
				return;
			}
		}
		buildParseException(new char[] {}, expected, new Validator[] {});
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String nexttok(String[] t, Validator[] v) throws Exception {
		tk = nexttok();
		validate(t);
		validate(v);
		return null;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String nexttok(Validator... verifiers) throws Exception {
		tk = this.nexttok();
		validate(verifiers);
		return tk;
	}

	/**
	 * @param verifiers
	 * @throws UnsupportedOperationException
	 * @throws ParseException
	 */
	protected void validate(Validator... verifiers)
			throws UnsupportedOperationException, ParseException {
		for (Validator verifier : verifiers) {
			if (verifier.validate(tk)) {
				return;
			}
		}
		buildParseException(new char[] {}, new String[] {}, verifiers);
	}

	/**
	 * @param verifiers
	 * @throws ParseException
	 */
	public void buildParseException(char[] chrs, String[] tokens,
			Validator[] verifiers) throws ParseException {
		ParseException pex = new ParseException();
		pex.setFileName(getSourceName());
		pex.setColumn(getColumn());
		pex.setLine(getLine());
		pex.setLineNumber(getLineNumber());
		pex.setExpected(chrs);
		pex.setExpected(tokens);
		pex.setExpected(verifiers);
		pex.setToken(tk);
		throw pex;
	}

	/**
	 * Set up the alpha numeric verifier for.
	 * 
	 * @param alphaNumericVerifier
	 *            the verifier
	 */
	public void setAlphaNumericVerifier(Validator alphaNumericVerifier) {
		this.alphaNumericVerifier = alphaNumericVerifier;
	}

	/**
	 * Set up the alpha verifier for.
	 * 
	 * @param alphaNumericVerifier
	 *            the verifier
	 */
	public void setAlphaVerifier(Validator alphaVerifier) {
		this.alphaVerifier = alphaVerifier;
	}

	/**
	 * Set up the numeric verifier for.
	 * 
	 * @param alphaNumericVerifier
	 *            the verifier
	 */
	public void setNumericVerifier(Validator numericVerifier) {
		this.numericVerifier = numericVerifier;
	}

	/**
	 * Set up the space verifier for.
	 * 
	 * @param alphaNumericVerifier
	 *            the verifier
	 */
	public void setSpaceVerifier(Validator spaceVerifier) {
		this.spaceVerifier = spaceVerifier;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String skipMultiLineComment() throws Exception {
		StringBuffer comment = new StringBuffer();
		char chr, nextChar;

		do {
			chr = nextchr();
			if (chr == '*') {
				nextChar = nextchr();
				if (nextChar == '/') {
					return comment.toString();
				}
				comment.append(chr).append(nextChar);
			} else {
				comment.append(chr);
			}
		} while (true);
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String skipQutedString() throws Exception {
		char chr = '\"';
		StringBuffer buffer = new StringBuffer();
		buffer.append(chr);
		chr = nextchr();

		if (chr == '\"') {
			return "\"\"";
		}
		do {
			buffer.append(chr);
			if (chr == '\\') {
				chr = nextchr();
				buffer.append(chr);
			}
			chr = nextchr();
		} while (chr != '\"');
		buffer.append('\"');
		return buffer.toString();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String skipSingleLineComment() throws Exception {
		int currColumn = inputSource.getColumn();
		String line = inputSource.getLine();
		inputSource.nextLine();
		return line.substring(currColumn, line.length());
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String skipTo(char symbol) throws Exception {
		char chr, counterpart;
		int count = 1;
		StringBuffer buffer = new StringBuffer();

		counterpart = getCounterpart(symbol);
		do {
			chr = nextchr();
			if (chr == symbol) {
				count++;
				buffer.append(chr);
			} else if (chr == counterpart) {
				count--;
				buffer.append(chr);
			} else if (chr == '\"') {
				buffer.append(skipQutedString());
			} else {
				buffer.append(chr);
			}
		} while (count > 0);
		buffer.insert(0, symbol);
		return buffer.toString();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public void mark() throws Exception {
		inputSource.mark();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public void reset() throws Exception {
		inputSource.reset();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String getPosition() {
		return this.getSourceName() + " [" + getLineNumber() + "|"
				+ getColumn() + "]";
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public String skipTo(String token) throws Exception {
		String tk;
		int count = 0;
		StringBuffer buffer = new StringBuffer();

		do {
			tk = nexttok();
			if (tk.equals("{")) {
				count++;
				buffer.append(" ").append(tk);
			} else if (tk.equals("}")) {
				count--;
				buffer.append(" ").append(tk);
			} else if (token.equals('\"')) {
				buffer.append(" ").append(skipQutedString());
			} else if (tk.equals("+") || tk.equals("-") || tk.equals("/")
					|| tk.equals("*") || tk.equals("\\") || tk.equals("%")
					|| tk.equals("&") || tk.equals("|") || tk.equals("!")
					|| tk.equals("^") || tk.equals("=") || tk.equals("\"")) {
				buffer.append(tk);
			} else {
				buffer.append(" ").append(tk);
			}
		} while (!(count == 0 && tk.equals(token)));
		// buffer.insert(0, tk);
		return buffer.toString();
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public CharInputSequence getCharInputSequence() {
		return inputSource;
	}

	/**
	 * Get the next token with the constraints.
	 * 
	 * @param validators
	 *            the constraints
	 */
	public void setCharInputSequence(CharInputSequence charInputSequence) {
		this.inputSource = charInputSequence;
	}
}
